import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FileDialog;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.io.File;

import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

public class ImageFrame implements ActionListener
{
	private JButton closeButton;
	private JButton saveButton;
	private JCheckBox selectionCheckBox;
	private JFrame mainFrame;
	
	private ImagePane imagePane;
	
	public ImageFrame()
	{
		mainFrame = new JFrame("Capture");
		mainFrame.setPreferredSize(new Dimension(640, 480));
		mainFrame.setLayout(new BorderLayout());
		
		JScrollPane topScrollPane = new JScrollPane();
		mainFrame.add(topScrollPane, BorderLayout.CENTER);
		
		JPanel topPane = new JPanel(new FlowLayout());
		topScrollPane.setViewportView(topPane);
		
		imagePane = new ImagePane();
		imagePane.setBorder(BorderFactory.createLineBorder(Color.black));
		topPane.add(imagePane);
		
		JPanel buttonPane = new JPanel();
		mainFrame.add(buttonPane, BorderLayout.SOUTH);
		
		selectionCheckBox = new JCheckBox("Use selection");
		selectionCheckBox.addActionListener(this);
		buttonPane.add(selectionCheckBox);
		
		saveButton = new JButton("Save image...");
		saveButton.addActionListener(this);
		buttonPane.add(saveButton);
		
		closeButton = new JButton("Close");
		closeButton.addActionListener(this);
		buttonPane.add(closeButton);
	}
	
	public BufferedImage getImage()
	{
		return imagePane.image;
	}
	
	public BufferedImage getImageFromSelection()
	{
		return imagePane.image.getSubimage(
				imagePane.selection.x, imagePane.selection.y,
				imagePane.selection.width, imagePane.selection.height);
	}
	
	public void loadImage(BufferedImage image)
	{
		imagePane.loadImage(image);
		
		mainFrame.pack();
		mainFrame.setVisible(true);
	}
	
	@SuppressWarnings("serial")
	public class ImagePane extends JPanel implements MouseListener, MouseMotionListener
	{
		private BufferedImage image;
		private Point pointInSelection;
		private Rectangle selection;
		
		private int action;
		private boolean showSelection;
		
		// Action constants.
		private final int NO_ACTION = 0;
		private final int RESIZE = 1;
		private final int MOVE = 2;
		
		public ImagePane()
		{
			image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
			selection = new Rectangle(50, 50, 100, 100);
			pointInSelection = new Point(0, 0);
			
			action = NO_ACTION;
			showSelection = false;
			
			addMouseListener(this);
			addMouseMotionListener(this);
		}
		
		public void loadImage(BufferedImage image)
		{
			this.image = image;
			
			Dimension dim = new Dimension(
					image.getWidth(),
					image.getHeight());
			setPreferredSize(dim);
		}
		
		public void showSelection(boolean show)
		{
			this.showSelection = show;
			repaint();
		}
		
		public void paintComponent(Graphics g)
		{
			g.drawImage(image, 0, 0, null);
			
			if (showSelection) {
				// Prevents the selection rectangle from being painted
				// outside the screen.
				if (selection.x < 0) selection.x = 0;
				if (selection.y < 0) selection.y = 0;
				if (selection.x + selection.width > getSize().width)
					selection.x = getSize().width - selection.width - 1;
				if (selection.y + selection.height > getSize().height)
					selection.y = getSize().height - selection.height - 1;
				
				g.setColor(Color.RED);
				g.drawRect(
						selection.x, selection.y,
						selection.width, selection.height);
			}
		}
		
		public void mouseClicked(MouseEvent e)
		{
		}
		
		public void mouseEntered(MouseEvent e)
		{
		}
		
		public void mouseExited(MouseEvent e)
		{
		}
		
		public void mousePressed(MouseEvent e)
		{
			Point p = e.getPoint();
			
			if (getCursor() != Cursor.getDefaultCursor()) {
				action = RESIZE;
			} else if (selection.contains(p)) {
				pointInSelection.x = p.x - selection.x;
				pointInSelection.y = p.y - selection.y;
				action = MOVE;
			}
		}
		
		public void mouseReleased(MouseEvent e)
		{
			action = NO_ACTION;
		}
		
		public void mouseDragged(MouseEvent e)
		{
			Point p = e.getPoint();
			
			switch (action) {
			case RESIZE:
				if (getCursor() == Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR)) {
					// This if prevents the selection from being resized
					// outside the screen.
					if (p.x <= getSize().width && p.y <= getSize().height) {
						selection.width = p.x - selection.x;
						selection.height = p.y - selection.y;
					}
				} else if (getCursor() == Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR)) {
					if (p.x <= getSize().width && p.y >= 0) {
						selection.width = p.x - selection.x;
						selection.height = selection.height + selection.y - p.y;
						selection.y = p.y;
					}
				} else if (getCursor() == Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR)) {
					if (p.x >= 0 && p.y <= getSize().height) {
						selection.width = selection.width + selection.x - p.x;
						selection.x = p.x;
						selection.height = p.y - selection.y;
					}
				} else if (getCursor() == Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR)) {
					if (p.x >= 0 && p.y >= 0) {
						selection.width = selection.width + selection.x - p.x;
						selection.x = p.x;
						selection.height = selection.height + selection.y - p.y;
						selection.y = p.y;
					}
				}
				
				// Sets a size minimum limit.
				if (selection.width < 20) selection.width = 20;
				if (selection.height < 20) selection.height = 20;
				
				repaint();
				break;
			case MOVE:
				selection.x = p.x - pointInSelection.x;
				selection.y = p.y - pointInSelection.y;
				repaint();
				break;
			}
		}
		
		public void mouseMoved(MouseEvent e)
		{
			Point p = e.getPoint();
			
			// Selection resizing functionality below.
			// South-east edge.			
			if (p.x > (selection.x + selection.width - 5)
					&& p.x < (selection.x + selection.width + 5)
					&& p.y > (selection.y + selection.height - 5)
					&& p.y < (selection.y + selection.height + 5)) {
				setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
			// North-east edge.
			} else if (p.x > (selection.x + selection.width - 5)
					&& p.x < (selection.x + selection.width + 5)
					&& p.y > (selection.y - 5)
					&& p.y < (selection.y + 5)) {
				setCursor(Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR));
			// South-west edge.
			} else if (p.x > (selection.x - 5)
					&& p.x < (selection.x + 5)
					&& p.y > (selection.y + selection.height - 5)
					&& p.y < (selection.y + selection.height + 5)) {
				setCursor(Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR));
			// North-west edge.
			} else if (p.x > (selection.x - 5)
					&& p.x < (selection.x + 5)
					&& p.y > (selection.y - 5)
					&& p.y < (selection.y + 5)) {
				setCursor(Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR));
			} else {
				setCursor(Cursor.getDefaultCursor());
			}
		}
	}
	
	public void actionPerformed(ActionEvent e)
	{
		if (e.getSource() == selectionCheckBox) {
			imagePane.showSelection(selectionCheckBox.isSelected());
		} else if (e.getSource() == saveButton) {
			FileDialog fd = new FileDialog(mainFrame, "Save image...", FileDialog.SAVE);
			fd.setVisible(true);
			
			String fileName = fd.getFile();
			
			if (fileName == null || fileName.isEmpty())
				return;
			
			BufferedImage image = (imagePane.showSelection) ? getImageFromSelection() : getImage();
			File file = new File(fd.getFile());
			
			try {
				ImageIO.write(image, "png", file);
			} catch (Exception e1) {
				System.err.println("An error occured during image io!");
				System.exit(1);
			}
		} else if (e.getSource() == closeButton) {
			mainFrame.setVisible(false);
		}
	}
}